import "jstests/libs/query/sbe_assert_error_override.js";

import {
    anyEq,
    assertErrCodeAndErrMsgContains,
    assertErrorCode
} from "jstests/aggregation/extras/utils.js";

const coll = db.dateFromParts;

// Basic Sanity Checks
coll.drop();

assert.commandWorked(coll.insert([
    {_id: 0, year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713},
    {
        _id: 1,
        year: 2017,
        month: 6,
        day: 19,
        hour: 15,
        minute: 13,
        second: 25,
        millisecond: 713,
        timezone: "Europe/Amsterdam"
    },
    {
        _id: 2,
        year: 2017,
        month: 6,
        day: 19,
        hour: 15,
        minute: 13,
        second: 25,
        millisecond: 713,
        timezone: "Asia/Tokyo"
    },
    {
        _id: 3,
        date: {
            year: 2017,
            month: 6,
            day: 19,
            hour: 15,
            minute: 13,
            second: 25,
            millisecond: 713,
            timezone: "America/Chicago"
        }
    },
]));

assert(anyEq(
    [
        {_id: 1, date: ISODate("2016-12-31T23:00:00Z")},
        {_id: 2, date: ISODate("2016-12-31T15:00:00Z")},
    ],
    coll.aggregate([
            {
                $match: {'year': {$exists: true}, 'timezone': {$exists: true}},
            },
            {$project: {date: {'$dateFromParts': {year: "$year", "timezone": "$timezone"}}}}
        ])
        .toArray()));

assert.eq(
    [
        {_id: 3, date: ISODate("2017-06-19T05:00:00Z")},
    ],
    coll.aggregate([
            {
                $match: {
                    'date.year': {$exists: true},
                },
            },
            {
                $project: {
                    date: {
                        '$dateFromParts': {
                            year: "$date.year",
                            month: '$date.month',
                            day: '$date.day',
                            timezone: '$date.timezone'
                        }
                    }
                }
            }
        ])
        .toArray());

let pipeline = {$project: {date: {'$dateFromParts': "$date"}}};
assertErrorCode(coll, pipeline, 40519);

pipeline = {
    $project: {date: {'$dateFromParts': {"timezone": "$timezone"}}}
};
assertErrorCode(coll, pipeline, 40516);

pipeline = {
    $project: {date: {'$dateFromParts': {year: false}}}
};
assertErrorCode(coll, pipeline, 40515);

pipeline = {
    $project: {date: {'$dateFromParts': {year: 2012, "timezone": "DoesNot/Exist"}}}
};
assertErrorCode(coll, pipeline, 40485);

pipeline = {
    $project: {date: {'$dateFromParts': {year: 2012, "timezone": 5}}}
};
assertErrorCode(coll, pipeline, 40517);

coll.drop();

assert.commandWorked(coll.insert([
    {
        _id: 0,
        year: 2017,
        month: 6,
        day: 23,
        hour: 14,
        minute: 27,
        second: 37,
        millisecond: 742,
        timezone: "Europe/Berlin"
    },
]));

let pipelines = [
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "Europe/Berlin",
                    year: 2017,
                    month: 6,
                    day: 23,
                    hour: 14,
                    minute: 27,
                    second: 37,
                    millisecond: 742
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "Europe/Berlin",
                    year: NumberInt("2017"),
                    month: NumberInt("6"),
                    day: NumberInt("23"),
                    hour: NumberInt("14"),
                    minute: NumberInt("27"),
                    second: NumberInt("37"),
                    millisecond: NumberInt("742")
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "Europe/Berlin",
                    year: NumberLong("2017"),
                    month: NumberLong("6"),
                    day: NumberLong("23"),
                    hour: NumberLong("14"),
                    minute: NumberLong("27"),
                    second: NumberLong("37"),
                    millisecond: NumberLong("742")
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "Europe/Berlin",
                    year: NumberDecimal("2017"),
                    month: NumberDecimal("6"),
                    day: NumberDecimal("23"),
                    hour: NumberDecimal("14"),
                    minute: NumberDecimal("27"),
                    second: NumberDecimal("37"),
                    millisecond: NumberDecimal("742")
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "+02:00",
                    year: 2017,
                    month: 6,
                    day: 23,
                    hour: 14,
                    minute: 27,
                    second: 37,
                    millisecond: 742
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "-02",
                    year: 2017,
                    month: 6,
                    day: 23,
                    hour: 10,
                    minute: 27,
                    second: 37,
                    millisecond: 742
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "+02:00",
                    year: 2017,
                    month: 6,
                    day: 23,
                    hour: 14,
                    minute: 27,
                    second: 37,
                    millisecond: 742
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "+04:15",
                    year: 2017,
                    month: 6,
                    day: 23,
                    hour: 16,
                    minute: 42,
                    second: 37,
                    millisecond: 742
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "$timezone",
                    year: 2017,
                    month: 6,
                    day: 23,
                    hour: 14,
                    minute: 27,
                    second: 37,
                    millisecond: 742
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "Europe/Berlin",
                    year: "$year",
                    month: 6,
                    day: 23,
                    hour: 14,
                    minute: 27,
                    second: 37,
                    millisecond: 742
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "Europe/Berlin",
                    year: 2017,
                    month: "$month",
                    day: 23,
                    hour: 14,
                    minute: 27,
                    second: 37,
                    millisecond: 742
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "Europe/Berlin",
                    year: 2017,
                    month: 6,
                    day: "$day",
                    hour: 14,
                    minute: 27,
                    second: 37,
                    millisecond: 742
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "Europe/Berlin",
                    year: 2017,
                    month: 6,
                    day: 23,
                    hour: "$hour",
                    minute: 27,
                    second: 37,
                    millisecond: 742
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "Europe/Berlin",
                    year: 2017,
                    month: 6,
                    day: 23,
                    hour: 14,
                    minute: "$minute",
                    second: 37,
                    millisecond: 742
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "Europe/Berlin",
                    year: 2017,
                    month: 6,
                    day: 23,
                    hour: 14,
                    minute: 27,
                    second: "$second",
                    millisecond: 742
                }
            }
        }
    }],
    [{
        '$project': {
            date: {
                '$dateFromParts': {
                    timezone: "Europe/Berlin",
                    year: 2017,
                    month: 6,
                    day: 23,
                    hour: 14,
                    minute: 27,
                    second: 37,
                    millisecond: "$millisecond"
                }
            }
        }
    }],
];

pipelines.forEach(function(pipeline) {
    assert.eq([{_id: 0, date: ISODate("2017-06-23T12:27:37.742Z")}],
              coll.aggregate(pipeline).toArray(),
              tojson(pipeline));
});

// Testing whether $dateFromParts throws the right assert for missing values

coll.drop();

assert.commandWorked(coll.insert([
    {_id: 0},
]));

pipelines = [
    [{'$project': {date: {'$dateFromParts': {year: "$year"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 2017, month: "$month"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 2017, day: "$day"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 2017, hour: "$hour"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 2017, minute: "$minute"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 2017, second: "$second"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 2017, millisecond: "$millisecond"}}}}],
    [{'$project': {date: {'$dateFromParts': {isoWeekYear: "$isoWeekYear"}}}}],
    [{'$project': {date: {'$dateFromParts': {isoWeekYear: 2017, isoWeek: "$isoWeek"}}}}],
    [{'$project': {date: {'$dateFromParts': {isoWeekYear: 2017, isoDayOfWeek: "$isoDayOfWeek"}}}}],
];

pipelines.forEach(function(pipeline) {
    assert.eq([{_id: 0, date: null}], coll.aggregate(pipeline).toArray(), tojson(pipeline));
});

pipeline = [{'$project': {date: {'$dateFromParts': {year: 2017, timezone: "$timezone"}}}}];
assert.eq([{_id: 0, date: null}], coll.aggregate(pipeline).toArray());

// Testing whether it throws the right assert for uncoersable values

coll.drop();

assert.commandWorked(coll.insert([
    {_id: 0, falseValue: false},
]));

pipelines = [
    [{'$project': {date: {'$dateFromParts': {year: "$falseValue"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 2017, month: "$falseValue"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 2017, day: "$falseValue"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 2017, hour: "$falseValue"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 2017, minute: "$falseValue"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 2017, second: "$falseValue"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 2017, millisecond: "$falseValue"}}}}],
    [{'$project': {date: {'$dateFromParts': {isoWeekYear: "$falseValue"}}}}],
    [{'$project': {date: {'$dateFromParts': {isoWeekYear: 2017, isoWeek: "$falseValue"}}}}],
    [{'$project': {date: {'$dateFromParts': {isoWeekYear: 2017, isoDayOfWeek: "$falseValue"}}}}],
];

pipelines.forEach(function(pipeline) {
    assertErrorCode(coll, pipeline, 40515);
});

pipeline = [{'$project': {date: {'$dateFromParts': {year: 2017, timezone: "$falseValue"}}}}];
assertErrorCode(coll, pipeline, 40517);

// Testing whether it throws the right assert for uncoersable values

coll.drop();

assert.commandWorked(coll.insert([
    {_id: 0, outOfRangeValue: 10002},
]));

pipelines = [
    [{'$project': {date: {'$dateFromParts': {year: "$outOfRangeValue"}}}}],
    [{'$project': {date: {'$dateFromParts': {year: -1}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 0}}}}],
    [{'$project': {date: {'$dateFromParts': {year: 10000}}}}],
];

pipelines.forEach(function(pipeline) {
    assertErrorCode(coll, pipeline, 40523);
});

// Testing "out of range" under and overflows

coll.drop();

assert.commandWorked(coll.insert([{
    _id: 0,
    minusOne: -1,
    zero: 0,
    one: 1,
    thirteen: 13,
    twentyFive: 25,
    sixtyOne: 61,
    thousandAndOne: 1001,
    tenThousandMinusOne: 9999,
    tenThousandAndOne: 10001,
    seventyMillionAndSomething: 71841012,
    secondsSinceEpoch: 1502095918,
    millisSinceEpoch: NumberLong("1502095918551"),
}]));

let tests = [
    {expected: "0001-01-01T00:00:00.000Z", parts: {year: "$one"}},
    {expected: "9999-01-01T00:00:00.000Z", parts: {year: "$tenThousandMinusOne"}},
    {expected: "2016-11-01T00:00:00.000Z", parts: {year: 2017, month: "$minusOne"}},
    {expected: "2016-12-01T00:00:00.000Z", parts: {year: 2017, month: "$zero"}},
    {expected: "2018-01-01T00:00:00.000Z", parts: {year: 2017, month: "$thirteen"}},
    {expected: "2016-12-30T00:00:00.000Z", parts: {year: 2017, day: "$minusOne"}},
    {expected: "2016-12-31T00:00:00.000Z", parts: {year: 2017, day: "$zero"}},
    {expected: "2017-03-02T00:00:00.000Z", parts: {year: 2017, day: "$sixtyOne"}},
    {expected: "2016-12-31T23:00:00.000Z", parts: {year: 2017, hour: "$minusOne"}},
    {expected: "2017-01-02T01:00:00.000Z", parts: {year: 2017, hour: "$twentyFive"}},
    {expected: "2016-12-31T23:59:00.000Z", parts: {year: 2017, minute: "$minusOne"}},
    {expected: "2017-01-01T00:00:00.000Z", parts: {year: 2017, minute: "$zero"}},
    {expected: "2017-01-01T01:01:00.000Z", parts: {year: 2017, minute: "$sixtyOne"}},
    {expected: "2016-12-31T23:59:59.000Z", parts: {year: 2017, second: "$minusOne"}},
    {expected: "2017-01-01T00:01:01.000Z", parts: {year: 2017, second: "$sixtyOne"}},
    {
        expected: "2019-04-12T11:50:12.000Z",
        parts: {year: 2017, second: "$seventyMillionAndSomething"}
    },
    {
        expected: "1972-04-11T11:50:12.000Z",
        parts: {year: 1970, second: "$seventyMillionAndSomething"}
    },
    {expected: "2017-08-07T08:51:58.000Z", parts: {year: 1970, second: "$secondsSinceEpoch"}},
    {expected: "2016-12-31T23:59:59.999Z", parts: {year: 2017, millisecond: "$minusOne"}},
    {expected: "2017-01-01T00:00:01.001Z", parts: {year: 2017, millisecond: "$thousandAndOne"}},
    {
        expected: "2017-01-01T19:57:21.012Z",
        parts: {year: 2017, millisecond: "$seventyMillionAndSomething"}
    },
    {expected: "2017-01-18T09:14:55.918Z", parts: {year: 2017, millisecond: "$secondsSinceEpoch"}},
    {
        expected: "1970-01-01T19:57:21.012Z",
        parts: {year: 1970, millisecond: "$seventyMillionAndSomething"}
    },
    {expected: "2017-08-07T08:51:58.551Z", parts: {year: 1970, millisecond: "$millisSinceEpoch"}},
];

tests.forEach(function(test) {
    assert.eq(
        [
            {_id: 0, date: ISODate(test.expected)},
        ],
        coll.aggregate([{$project: {date: {"$dateFromParts": test.parts}}}]).toArray(),
        tojson(test));
});

// Testing double and Decimal128 millisecond values that aren't representable as a 64-bit
// integer or overflow when converting to a 64-bit microsecond value.

coll.drop();

assert.commandWorked(coll.insert([{
    _id: 0,
    veryBigDoubleA: 18014398509481984.0,
    veryBigDecimal128A: NumberDecimal("9223372036854775807"),  // 2^63-1
    veryBigDoubleB: 18014398509481984000.0,
    veryBigDecimal128B: NumberDecimal("9223372036854775807000"),  // (2^63-1) * 1000
}]));

pipeline = [{$project: {date: {"$dateFromParts": {year: 1970, millisecond: "$veryBigDoubleA"}}}}];
assertErrCodeAndErrMsgContains(
    coll,
    pipeline,
    ErrorCodes.DurationOverflow,
    "Overflow casting from a lower-precision duration to a higher-precision duration");

// This doesn't throw unless I +1 to $veryBigDecimal128A
//
pipeline =
    [{$project: {date: {"$dateFromParts": {year: 1970, millisecond: "$veryBigDecimal128A"}}}}];
assertErrCodeAndErrMsgContains(
    coll,
    pipeline,
    ErrorCodes.DurationOverflow,
    "Overflow casting from a lower-precision duration to a higher-precision duration");

pipeline = [{$project: {date: {"$dateFromParts": {year: 1970, millisecond: "$veryBigDoubleB"}}}}];
assertErrCodeAndErrMsgContains(coll, pipeline, 40515, "'millisecond' must evaluate to an integer");

pipeline =
    [{$project: {date: {"$dateFromParts": {year: 1970, millisecond: "$veryBigDecimal128B"}}}}];
assertErrCodeAndErrMsgContains(coll, pipeline, 40515, "'millisecond' must evaluate to an integer");

// Testing that year values are only allowed in the range [0, 9999] and that month, day, hour,
// and minute values are only allowed in the range [-32,768, 32,767].
coll.drop();

assert.commandWorked(coll.insert(
    [{_id: 0, bigYear: 10000, smallYear: 0, prettyBigInt: 32768, prettyBigNegativeInt: -32769}]));

pipeline = [{$project: {date: {"$dateFromParts": {year: "$bigYear"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 40523, "'year' must evaluate to an integer in the range 1 to 9999");

pipeline = [{$project: {date: {"$dateFromParts": {year: "$smallYear"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 40523, "'year' must evaluate to an integer in the range 1 to 9999");

pipeline = [{$project: {date: {"$dateFromParts": {year: 1970, month: "$prettyBigInt"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31034, "'month' must evaluate to a value in the range [-32768, 32767]");

pipeline = [{$project: {date: {"$dateFromParts": {year: 1970, month: "$prettyBigNegativeInt"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31034, "'month' must evaluate to a value in the range [-32768, 32767]");

pipeline = [{$project: {date: {"$dateFromParts": {year: 1970, month: 1, day: "$prettyBigInt"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31034, "'day' must evaluate to a value in the range [-32768, 32767]");

pipeline =
    [{$project: {date: {"$dateFromParts": {year: 1970, month: 1, day: "$prettyBigNegativeInt"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31034, "'day' must evaluate to a value in the range [-32768, 32767]");

pipeline = [{$project: {date: {"$dateFromParts": {year: 1970, hour: "$prettyBigInt"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31034, "'hour' must evaluate to a value in the range [-32768, 32767]");

pipeline = [{$project: {date: {"$dateFromParts": {year: 1970, hour: "$prettyBigNegativeInt"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31034, "'hour' must evaluate to a value in the range [-32768, 32767]");

pipeline = [{$project: {date: {"$dateFromParts": {year: 1970, hour: 0, minute: "$prettyBigInt"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31034, "'minute' must evaluate to a value in the range [-32768, 32767]");

pipeline = [
    {$project: {date: {"$dateFromParts": {year: 1970, hour: 0, minute: "$prettyBigNegativeInt"}}}}
];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31034, "'minute' must evaluate to a value in the range [-32768, 32767]");

pipeline = [{$project: {date: {"$dateFromParts": {isoWeekYear: "$bigYear"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31095, "'isoWeekYear' must evaluate to an integer in the range 1 to 9999");

pipeline = [{$project: {date: {"$dateFromParts": {isoWeekYear: "$bigYear"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31095, "'isoWeekYear' must evaluate to an integer in the range 1 to 9999");

pipeline = [{$project: {date: {"$dateFromParts": {isoWeekYear: "$smallYear"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31095, "'isoWeekYear' must evaluate to an integer in the range 1 to 9999");

pipeline = [{$project: {date: {"$dateFromParts": {isoWeekYear: 1970, isoWeek: "$prettyBigInt"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31034, "'isoWeek' must evaluate to a value in the range [-32768, 32767]");

pipeline =
    [{$project: {date: {"$dateFromParts": {isoWeekYear: 1970, isoWeek: "$prettyBigNegativeInt"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31034, "'isoWeek' must evaluate to a value in the range [-32768, 32767]");

pipeline =
    [{$project: {date: {"$dateFromParts": {isoWeekYear: 1970, isoDayOfWeek: "$prettyBigInt"}}}}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31034, "'isoDayOfWeek' must evaluate to a value in the range [-32768, 32767]");

pipeline = [{
    $project: {date: {"$dateFromParts": {isoWeekYear: 1970, isoDayOfWeek: "$prettyBigNegativeInt"}}}
}];
assertErrCodeAndErrMsgContains(
    coll, pipeline, 31034, "'isoDayOfWeek' must evaluate to a value in the range [-32768, 32767]");

// Testing wrong arguments

coll.drop();

assert.commandWorked(coll.insert([
    {_id: 0},
]));

pipelines = [
    {code: 40519, pipeline: {'$project': {date: {'$dateFromParts': true}}}},
    {code: 40519, pipeline: {'$project': {date: {'$dateFromParts': []}}}},

    {code: 40518, pipeline: {'$project': {date: {'$dateFromParts': {unknown: true}}}}},

    {code: 40516, pipeline: {'$project': {date: {'$dateFromParts': {}}}}},

    {
        code: 40489,
        pipeline: {'$project': {date: {'$dateFromParts': {year: 2017, isoWeekYear: 2017}}}}
    },
    {code: 40489, pipeline: {'$project': {date: {'$dateFromParts': {year: 2017, isoWeek: 3}}}}},
    {
        code: 40489,
        pipeline: {'$project': {date: {'$dateFromParts': {year: 2017, isoDayOfWeek: 5}}}}
    },
    {
        code: 40489,
        pipeline: {'$project': {date: {'$dateFromParts': {isoWeekYear: 2017, year: 2017}}}}
    },

    {
        code: 40525,
        pipeline: {'$project': {date: {'$dateFromParts': {isoWeekYear: 2017, month: 12}}}}
    },
    {code: 40525, pipeline: {'$project': {date: {'$dateFromParts': {isoWeekYear: 2017, day: 17}}}}},
];

pipelines.forEach(function(item) {
    assertErrorCode(coll, item.pipeline, item.code, tojson(pipeline));
});

// Testing wrong value (types)
coll.drop();

assert.commandWorked(coll.insert([
    {_id: 0, floatField: 2017.5, decimalField: NumberDecimal("2017.5")},
]));

pipelines = [
    {code: 40515, pipeline: {'$project': {date: {'$dateFromParts': {year: "2017"}}}}},
    {code: 40515, pipeline: {'$project': {date: {'$dateFromParts': {year: 2017.3}}}}},
    {
        code: 40515,
        pipeline: {'$project': {date: {'$dateFromParts': {year: NumberDecimal("2017.3")}}}}
    },
    {code: 40515, pipeline: {'$project': {date: {'$dateFromParts': {year: "$floatField"}}}}},
    {code: 40515, pipeline: {'$project': {date: {'$dateFromParts': {year: "$decimalField"}}}}},
];

pipelines.forEach(function(item) {
    assertErrorCode(coll, [item.pipeline], item.code);
});

coll.drop();

assert.commandWorked(coll.insert([
    {_id: 0, year: NumberDecimal("2017"), month: 6.0, day: NumberInt(19), hour: NumberLong(15)},
    {
        _id: 1,
        year: NumberDecimal("2017"),
        minute: 6.0,
        second: NumberInt(19),
        millisecond: NumberLong(15)
    },
    {_id: 2, isoWeekYear: NumberDecimal("2017"), isoWeek: 6.0, isoDayOfWeek: NumberInt(4)},
]));

assert.eq(
    [
        {_id: 0, date: ISODate("2017-06-19T15:00:00Z")},
    ],
    coll.aggregate([
            {
                $match: {_id: 0},
            },
            {
                $project: {
                    date: {
                        '$dateFromParts':
                            {year: "$year", month: "$month", day: "$day", hour: "$hour"}
                    }
                }
            }
        ])
        .toArray());

assert.eq(
    [
        {_id: 1, date: ISODate("2017-01-01T00:06:19.015Z")},
    ],
    coll.aggregate([
            {
                $match: {_id: 1},
            },
            {
                $project: {
                    date: {
                        '$dateFromParts': {
                            year: "$year",
                            minute: "$minute",
                            second: "$second",
                            millisecond: "$millisecond"
                        }
                    }
                }
            }
        ])
        .toArray());

assert.eq(
    [
        {_id: 2, date: ISODate("2017-02-09T00:00:00Z")},
    ],
    coll.aggregate([
            {
                $match: {_id: 2},
            },
            {
                $project: {
                    date: {
                        '$dateFromParts': {
                            isoWeekYear: "$isoWeekYear",
                            isoWeek: "$isoWeek",
                            isoDayOfWeek: "$isoDayOfWeek"
                        }
                    }
                }
            }
        ])
        .toArray());

coll.drop();

assert.commandWorked(coll.insert([
    {
        _id: 0,
        year: NumberDecimal("2017"),
        month: 6.0,
        day: NumberInt(19),
        hour: NumberLong(15),
        minute: NumberDecimal(1),
        second: 51,
        millisecond: 551
    },
]));

tests = [
    {expected: ISODate("2017-06-19T19:01:51.551Z"), tz: "-04:00"},
    {expected: ISODate("2017-06-19T12:01:51.551Z"), tz: "+03"},
    {expected: ISODate("2017-06-19T18:21:51.551Z"), tz: "-0320"},
    {expected: ISODate("2017-06-19T19:01:51.551Z"), tz: "America/New_York"},
    {expected: ISODate("2017-06-19T13:01:51.551Z"), tz: "Europe/Amsterdam"},
];

tests.forEach(function(test) {
    assert.eq(
        [
            {_id: 0, date: test.expected},
        ],
        coll.aggregate([{
                $project: {
                    date: {
                        "$dateFromParts": {
                            year: "$year",
                            month: "$month",
                            day: "$day",
                            hour: "$hour",
                            minute: "$minute",
                            second: "$second",
                            millisecond: "$millisecond",
                            timezone: test.tz
                        }
                    }
                }
            }])
            .toArray(),
        tojson(test));
});

coll.drop();

assert.commandWorked(coll.insert([
    {
        _id: 0,
        isoWeekYear: NumberDecimal("2017"),
        isoWeek: 25.0,
        isoDayOfWeek: NumberInt(1),
        hour: NumberLong(15),
        minute: NumberDecimal(1),
        second: 51,
        millisecond: 551
    },
]));

tests = [
    {expected: ISODate("2017-06-19T19:01:51.551Z"), tz: "-04:00"},
    {expected: ISODate("2017-06-19T12:01:51.551Z"), tz: "+03"},
    {expected: ISODate("2017-06-19T18:21:51.551Z"), tz: "-0320"},
    {expected: ISODate("2017-06-19T19:01:51.551Z"), tz: "America/New_York"},
    {expected: ISODate("2017-06-19T13:01:51.551Z"), tz: "Europe/Amsterdam"},
];

tests.forEach(function(test) {
    assert.eq(
        [
            {_id: 0, date: test.expected},
        ],
        coll.aggregate([{
                $project: {
                    date: {
                        "$dateFromParts": {
                            isoWeekYear: "$isoWeekYear",
                            isoWeek: "$isoWeek",
                            isoDayOfWeek: "$isoDayOfWeek",
                            hour: "$hour",
                            minute: "$minute",
                            second: "$second",
                            millisecond: "$millisecond",
                            timezone: test.tz
                        }
                    }
                }
            }])
            .toArray(),
        tojson(test));
});
